package vek.ObjectDatabase;

import java.util.ArrayList;
import java.util.Observable;
import vek.Objects.POI;

/**
 * Provides a unified access point to the databases for the rest of the application.
 * This class can handle multiple databases and thus has redudancy and uses the database which is available.
 * Forwards all searches made to the database which has the highest priority and is available.
 * This class also takes care of updating since it is the class that has contact to the rest of the application,
 * therefore when the XML files are updated from the server, it class use notifyObservers to notify all observers of the changes.
 * @author Christian, Michal og Jakob (Hold 1, gruppe 2)
 */
public class DatabaseConnector extends Observable{
    
    private ArrayList<POIDatabase> poiDatabases; // the POI databases this connector has access to
    private POIDatabase defaultPOIDatabase; // the default database, this should be the one with highest priority
    
    public enum DatabaseType { POI } // the types of databases
 /**
 * Creates a DatabaseConnector containing an empty list of POIDatabases.
 */
    public DatabaseConnector()
    {
        poiDatabases = new ArrayList<POIDatabase>();
    }
    
    /**
     * Gets all the POIs from the best database using selectDatabaseToUse()
     * @return an ArrayList of all the POIs in the database
     */
    
    public ArrayList<POI> getAll()
    {
        POIDatabase database = selectDatabaseToUse();
        if(database != null)
        {
            return database.getAll();
        }
        else
        {
            return new ArrayList<POI>();
        }
    }
    
    /**
     * Forwards the search to the best possible database using the selectDatabaseToUse() method.
     * @param se the Search object which contains the search parameters which define which kind of POIs that should be searched for
     * @return all the POIs that match the search criteria.
     */
    
    public ArrayList<POI> searchFor(Search se)
    {
        POIDatabase database = selectDatabaseToUse();
        if(database != null)
        {
            return database.searchFor(se);
        }
        else
        {
            return new ArrayList<POI>();
        }
    }
    
    /**
     * Selects the database to use for the database communication.
     * This method selects the default database if it is avaialble, 
     * and if not gets the next available database using the getNextAvailableDatabase() method
     * @return the database which is to be used
     */
    
    private POIDatabase selectDatabaseToUse()
    {
        if(isDatabaseAvailable(defaultPOIDatabase)) // checks if the database is available, that is ready to be used
        {
            return defaultPOIDatabase;
        }
        else
        {
            POIDatabase nextAvailDatabase = getNextAvailableDatabase();
            if(nextAvailDatabase != null)
            {
                return nextAvailDatabase;
            }
            else
            {
                assert false : "DBC: error: no databases available";
                return null;
            }
        }
    }
    
    /**
     * gets all the types of POIs using the getAllTypes method in POIDatabase.
     * For example: "Museum", "Restaurant" etc
     * @return an ArrayList containg all the types
     */
    
    public ArrayList<String> getAllTypes()
    {   
        POIDatabase database = selectDatabaseToUse();
        if(database != null)
        {
            return database.getAllTypes();
        }
        else
        {
            return new ArrayList<String>();
        }
    }
    
    /**
     * gets the next available database with the highest priority.
     * @return the database with the highest priority which is also avaialble
     */
    
    private POIDatabase getNextAvailableDatabase()
    {
        int priority = 0;
        POIDatabase returnedPOIDatabase = null;
        
        for(int i = 0; i < poiDatabases.size(); i++)
        {
            if(poiDatabases.get(i) != null) // checks for NullPointer errors in case a null database has been added 
            {
               if( poiDatabases.get(i).isAvailable() ) // checks if the database is ready to be used
               {
                    if(poiDatabases.get(i).getPriority() > priority) // checks if the current database has a higher priority than the database with the highest priority that has been iterated in this loop.
                    {
                        priority = poiDatabases.get(i).getPriority();
                        returnedPOIDatabase = poiDatabases.get(i);
                    }
               }
            }
        }
        
        assert returnedPOIDatabase != null : "could not get next avail database in DBC"; // make sure the returned database is not null, thereby preventing NullPointer exceptions which are fatal for the program
        
        return returnedPOIDatabase;
    }
    
    /**
     * sets the default database. This is the database used for all transaction if it is available
     * @return true if a default database could be set
     */
    
    private boolean setDefaultDatabase()
    {
        int priority = 0;
        POIDatabase newDefaultDatabase = null;
        
        for(int i = 0; i < poiDatabases.size(); i++)
        {
            if(poiDatabases.get(i) != null)
            {
                if(poiDatabases.get(i).getPriority() > priority)
                {
                    priority = poiDatabases.get(i).getPriority();
                    newDefaultDatabase = poiDatabases.get(i);
                }
            }
        }
        if(newDefaultDatabase != null)
        {
            defaultPOIDatabase = newDefaultDatabase;
            return true;
        }
        else
        {
            assert false : "could not get default database";
            return false;
        }
        
    }
    
    /**
     * Adds a database then runs the setDefaultDatabase method() to make sure that the database with the highest priority is the default database
     * @param dbs
     */
    
    public void addDatabase(POIDatabase dbs)
    {
        poiDatabases.add(dbs);
        setDefaultDatabase();
    }
    
    /**
     * Checks if a database is available.
     * This method checks if a database is available by first chekcing if it is instantiated (that is not null) 
     * then runs the isAvaialbe method in the database, thereby allowing the database to "excuse itself" that is, to make itself unavailable incase there are problems
     * @param dbs the database to be checked
     * @return true if the database is avaialble
     */
    
    public boolean isDatabaseAvailable(POIDatabase dbs)
    {
        if(dbs == null)
        {
            return false;
        }
        else if(dbs.isAvailable() == true)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    
    /**
     * Updates the SimpleDatabases by loading a new set of POIs into them, then calls notifies the observers.
     * @param allnewPOIs the POIs which should be loaded into the SimpleDatabases
     */
    
    public void updateSimpleDatabases(ArrayList<POI> allnewPOIs)
    {
        for(int i = 0; i < poiDatabases.size(); i++)
        {
            if(poiDatabases.get(i) != null)
            {
               if(poiDatabases.get(i).getType() == DatabaseType.POI)
               {
                   SimpleDatabase currentDatabase = (SimpleDatabase) poiDatabases.get(i);
                   currentDatabase.update(allnewPOIs);
               }
            }
        }
        
        setChanged();
        notifyObservers(); // makes all the observers aware that a change has occured, that is the XML files have been updated.
        
    }

}
